package com.liaoyifan.core.config;

import com.liaoyifan.core.constant.Constants;
import com.liaoyifan.core.constant.Properties;
import com.liaoyifan.core.model.Result;
import com.liaoyifan.core.opera.OpenApiService;
import com.liaoyifan.core.util.RequestContext;
import io.swagger.v3.core.converter.ModelConverters;
import io.swagger.v3.oas.annotations.OpenAPIDefinition;
import io.swagger.v3.oas.annotations.info.Info;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.Operation;
import io.swagger.v3.oas.models.media.Content;
import io.swagger.v3.oas.models.media.MediaType;
import io.swagger.v3.oas.models.parameters.HeaderParameter;
import io.swagger.v3.oas.models.responses.ApiResponse;
import io.swagger.v3.oas.models.responses.ApiResponses;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import org.springdoc.core.customizers.GlobalOpenApiCustomizer;
import org.springdoc.core.customizers.GlobalOperationCustomizer;
import org.springdoc.core.customizers.OpenApiBuilderCustomizer;
import org.springdoc.core.customizers.ServerBaseUrlCustomizer;
import org.springdoc.core.properties.SpringDocConfigProperties;
import org.springdoc.core.providers.JavadocProvider;
import org.springdoc.core.service.SecurityService;
import org.springdoc.core.utils.PropertyResolverUtils;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;

@SuppressWarnings("unused")
@Configuration
@OpenAPIDefinition(info = @Info(title = "OpenAPI Doc", version = "v1"))
@ConditionalOnProperty(name = Properties.OPEN_API_ENABLE, havingValue = Constants.TRUE)
public class OpenApiConfiguration {

    @SuppressWarnings("OptionalUsedAsFieldOrParameterType")
    @Bean
    public org.springdoc.core.service.OpenAPIService openApiService(
            Optional<OpenAPI> openAPI,
            SecurityService securityParser,
            SpringDocConfigProperties springDocConfigProperties,
            PropertyResolverUtils propertyResolverUtils,
            Optional<List<OpenApiBuilderCustomizer>> openApiBuilderCustomizers,
            Optional<List<ServerBaseUrlCustomizer>> serverBaseUrlCustomizers,
            Optional<JavadocProvider> javadocProvider) {
        return new OpenApiService(
                openAPI,
                securityParser,
                springDocConfigProperties,
                propertyResolverUtils,
                openApiBuilderCustomizers,
                serverBaseUrlCustomizers,
                javadocProvider);
    }

    @Bean
    public GlobalOperationCustomizer globalOperationCustomizer() {
        return (operation, handlerMethod) -> {
            if (handlerMethod.getReturnType().getParameterType() == void.class) {
                Content content = new Content();
                content.addMediaType(
                        org.springframework.http.MediaType.APPLICATION_JSON_VALUE,
                        new MediaType()
                                .schema(
                                        ModelConverters.getInstance()
                                                .readAll(Result.class)
                                                .get(Result.class.getSimpleName())));
                ApiResponse response = new ApiResponse();
                response.setDescription(HttpStatus.OK.name());
                response.setContent(content);
                ApiResponses apiResponses =
                        Optional.ofNullable(operation.getResponses()).orElse(new ApiResponses());
                apiResponses.addApiResponse(String.valueOf(HttpStatus.OK.value()), response);
                operation.setResponses(apiResponses);
            }
            return operation;
        };
    }

    @Bean
    public GlobalOpenApiCustomizer globalOpenApiCustomizer() {
        return openApi -> {
            if (openApi.getTags() != null) {
                openApi.getTags()
                        .sort(
                                Comparator.comparing(
                                        tag ->
                                                (int)
                                                        tag.getExtensions()
                                                                .getOrDefault(
                                                                        OpenApiService.ORDER,
                                                                        Integer.MAX_VALUE)));
            }
            if (openApi.getPaths() != null) {
                List<HeaderParameter> globalHeaders = new ArrayList<>();
                HeaderParameter headerParameter = new HeaderParameter();
                headerParameter.name(RequestContext.AUTHORIZATION);
                headerParameter.required(false);
                headerParameter.description(RequestContext.BEARER + "{token}");
                globalHeaders.add(headerParameter);
                openApi.getPaths()
                        .forEach(
                                (path, item) -> {
                                    processOperation(item.getGet(), globalHeaders);
                                    processOperation(item.getPost(), globalHeaders);
                                    processOperation(item.getPut(), globalHeaders);
                                    processOperation(item.getDelete(), globalHeaders);
                                    processOperation(item.getPatch(), globalHeaders);
                                    processOperation(item.getOptions(), globalHeaders);
                                    processOperation(item.getHead(), globalHeaders);
                                    processOperation(item.getTrace(), globalHeaders);
                                });
            }
        };
    }

    private void processOperation(Operation operation, List<HeaderParameter> headers) {
        if (operation != null) {
            headers.forEach(
                    header -> {
                        if (operation.getParameters() == null
                                || operation.getParameters().stream()
                                        .noneMatch(p -> p.getName().equals(header.getName()))) {
                            operation.addParametersItem(header);
                        }
                    });
        }
    }
}
